/******************************************************************************************************************************************/
/* Author: Shujun.Liu 
/* Email: duanhong1523@gmail.com
/* Create Data:03/01/2009
/* Reference: 
/*		1) Ernest Laurentin(elaurentin@sympatico.ca) 
/*		2) Project gears in Google code(http://code.google.com/p/gears/source/browse/trunk/third_party/AtlActiveScriptSite.h?r=3416)
/* Description:
/* This class implements an Active Script Hosting for client application
/*
/* This source code may be used for any purpose, including commercial applications, and may be modified or redistributed subject to the
/* following conditions:
/*
/* a) This notice may not be removed or changed in any source distribution.
/*
/* b) Altered source versions must be include a note to that effect,
/*    and must not be misrepresented as the original.
/*
/* c) The origin of this software may not be misrepresented - you may
/*    not claim to have written the original version. If you use this
/*    source in a product, an acknowledgement in the documentation
/*    would be appreciated, but is not required.
/* Version History:
/* 01/08/2008 copy the origin version from codeproject written by Ernest Laurentin
/* 03/01/2009 rewritte, make some improvement refer to Gears project host on Google code.
/******************************************************************************************************************************************/

#ifndef _ATLACTIVESCRIPTSITE_H__
#define _ATLACTIVESCRIPTSITE_H__

#ifndef __cplusplus
#error ATL requires C++ compilation (use a .cpp suffix)
#endif

#ifndef __ATLBASE_H__
#error atlcom.h requires atlbase.h to be included first
#endif

#include <string>
#include <map>
#include <activscp.h>
using namespace std;

class str_lower : public binary_function<wstring, wstring, bool> 
{
public:
	bool operator()(const wstring& x, const wstring& y) const
	{
		return (x.compare( y.c_str() ) < 0);
	}
};
typedef map<wstring, LPUNKNOWN, str_lower> IUnknownPtrMap;

#ifndef ACTIVS_HOST
#define ACTIVS_HOST      L"ScriptSite"
#endif
#define SCRIPTITEM_NAMEDITEM		(SCRIPTITEM_ISSOURCE | SCRIPTITEM_ISVISIBLE | SCRIPTITEM_GLOBALMEMBERS)
#define LANG_ENGLISH_NEUTRAL		(MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL))
#define LOCALE_SCRIPT_DEFAULT		(MAKELCID(LANG_ENGLISH_NEUTRAL, SORT_DEFAULT))

//////////////////////////////////////////////////////////////////////////////
// IActiveScriptSiteImpl
// "class T" must implement:
// LPUNKNOWN T::GetObjectPointer(const WCHAR* pstrName)
template <class T>
class ATL_NO_VTABLE IActiveScriptSiteImpl : public IActiveScriptSite
{
protected:
public:
	///////////////////////////////////////////////////////////////////////////
	// O P E R A T I O N S
	///////////////////////////////////////////////////////////////////////////

	STDMETHOD(GetLCID)(LCID* plcid)
	{
		*plcid = LOCALE_SCRIPT_DEFAULT;
		return S_OK;
	}

	STDMETHOD(GetItemInfo)(
		/* [in] */ LPCOLESTR pstrName,
		/* [in] */ DWORD dwReturnMask,
		/* [out] */LPUNKNOWN* ppiunkItem,
		/* [out] */LPTYPEINFO* ppti)
	{
		if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
		{
			if (!ppti)
				return E_INVALIDARG;
			*ppti = NULL;
		}

		if (dwReturnMask & SCRIPTINFO_IUNKNOWN)
		{
			if (!ppiunkItem)
				return E_INVALIDARG;
			*ppiunkItem = NULL;
		}

		// Required LPUNKNOWN T::GetObjectPointer(const WCHAR* pstrName)
		T* pT = static_cast<T*>(this);
		LPUNKNOWN lpUnknown = pT->GetObjectPointer( pstrName );
		//	ATLASSERT(NULL != lpUnknown);
		if (NULL == lpUnknown)
		{
			ATLTRACE(L"No IUnknown for item!\n");
			// This shouldn't ever happen, since we're providing the items
			return TYPE_E_ELEMENTNOTFOUND;
		}

		if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
		{
			// Use IProvideClassInfo to get ITypeInfo of coclass!
			IProvideClassInfo *pci = NULL;
			HRESULT hr = lpUnknown->QueryInterface(IID_IProvideClassInfo, (void**)&pci);
			if (SUCCEEDED(hr) && pci)
			{
				hr = pci->GetClassInfo(ppti);
			}

			// Release interface
			if (pci)
				pci->Release();

			if (FAILED(hr))
				return E_FAIL;
		}

		if (dwReturnMask & SCRIPTINFO_IUNKNOWN)
		{
			*ppiunkItem = lpUnknown;
			(*ppiunkItem)->AddRef();    // because returning
		}

		return S_OK;
	}

	STDMETHOD(GetDocVersionString)(BSTR* pbstrVersion)
	{
		USES_CONVERSION;
		*pbstrVersion = ::SysAllocString(T2OLE(ACTIVS_HOST));
		return NOERROR;

		ATLTRACENOTIMPL(L"IActiveScriptSiteImpl::GetDocVersionString\n");
	}

	STDMETHOD(OnScriptTerminate)(const VARIANT* /*pvarResult*/, const EXCEPINFO* /*pexcepinfo*/)
	{
		ATLTRACE(L"IActiveScriptSiteImpl::OnScriptTerminate\n");
		return S_OK;
	}

	STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState)
	{
		ATLTRACE(L"IActiveScriptSiteImpl::OnStateChange\n");
		return S_OK;
	}

	STDMETHOD(OnScriptError)(IActiveScriptError* pase )
	{
		if(pase==NULL)
			return E_POINTER;
		EXCEPINFO ei;
		HRESULT hr = pase->GetExceptionInfo(&ei);
		if (FAILED(hr))
		{
			return hr;
		}
		if(ei.pfnDeferredFillIn!=NULL) 
		{
			hr =( *(ei.pfnDeferredFillIn))(&ei);
			if (FAILED(hr))
			{
				return hr;
			}

		}
		// ORIGINAL VERSION:
		/*
		DWORD dwContext;
		ULONG ulLine;
		LONG lPos;
		HR(pase->GetSourcePosition(&dwContext,&ulLine,&lPos));
		CComBSTR src;
		HR(pase->GetSourceLineText(&src));
		*/
		// GOOGLE VERSION:
		// Don't exit if any of the GetSource*() functions fail;
		// always call the error handler, and provide whatever we can.
		DWORD dwContext = 0;
		ULONG ulLine = 0;
		LONG lPos = 0;
		pase->GetSourcePosition(&dwContext,&ulLine,&lPos);
		CComBSTR src;
		pase->GetSourceLineText(&src);
		// END CHANGES.
		return HandleScriptError(&ei,ulLine,lPos,src);

	}

	STDMETHOD(OnEnterScript)()
	{
		ATLTRACE(L"IActiveScriptSiteImpl::OnEnterScript\n");
		return S_OK;
	}

	STDMETHOD(OnLeaveScript)()
	{
		ATLTRACE(L"IActiveScriptSiteImpl::OnLeaveScript\n");
		return S_OK;
	}

	// This is an implementation method.
	// Override this method in your implementation to handle error messages
	STDMETHOD(HandleScriptError)(EXCEPINFO* pei,ULONG ulLine,LONG lPos,BSTR src)
	{
		WCHAR buf[1024];
		::wsprintf(buf,L"Script Error:\n\nSource: %ws\nError: %08X\nDescription: %ws\nLine: %d Column: %d\n\nCode:\n%ws",
			pei->bstrSource,pei->scode,pei->bstrDescription,ulLine+1,lPos,src);
		ATLTRACE(buf);
		return NOERROR;
	}


};


//////////////////////////////////////////////////////////////////////////////
// IActiveScriptHostImpl
template <class T>
class ATL_NO_VTABLE IActiveScriptHostImpl : public IActiveScriptSiteImpl<IActiveScriptHostImpl<T> >
{
public:
	// Constructors
	IActiveScriptHostImpl()
	{
	}
	virtual ~IActiveScriptHostImpl()
	{
		ReleaseObjects();
	}

	void ReleaseObjects()
	{
		if (m_spAxsScript != NULL)
			m_spAxsParse = NULL;

		if (m_spAxsScript != NULL)
		{
			m_spAxsScript->Close();
			m_spAxsScript = NULL;
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// O V E R R I D E
	///////////////////////////////////////////////////////////////////////////

	virtual LPUNKNOWN GetObjectPointer(const WCHAR* pstrName)
	{
		LPUNKNOWN lpUnknown = NULL;
		wstring sNamedItem = pstrName;
		IUnknownPtrMap::iterator iter = m_mapNamedItems.find( sNamedItem );
		if ( iter != m_mapNamedItems.end())
			lpUnknown = (*iter).second;
		return lpUnknown;
	}

	///////////////////////////////////////////////////////////////////////////
	// O P E R A T I O N S
	///////////////////////////////////////////////////////////////////////////

	///////////////////////////////////////////////////////////////////////////
	// CreateEngine
	BOOL CreateEngine(LPCOLESTR pstrProgID)
	{
		// Auto deletion of script engine
		ReleaseObjects();

		CLSID clsid;
		// Search for LanguageID and create instance of script engine
		if (SUCCEEDED(CLSIDFromProgID(pstrProgID, &clsid)))
		{
			// If this happens, the scripting engine is probably not properly registered
			HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IActiveScript, (void **)&m_spAxsScript);
			// Script Engine must support IActiveScriptParse for us to use it
			if (SUCCEEDED(hr))
				hr = m_spAxsScript->QueryInterface(IID_IActiveScriptParse, (void **)&m_spAxsParse);

			if (SUCCEEDED(hr))
				hr = m_spAxsScript->SetScriptSite( (IActiveScriptSiteImpl<IActiveScriptHostImpl>*) this);

			// InitNew the object:
			if (SUCCEEDED(hr))
				hr = m_spAxsParse->InitNew();

			// Store 'Host' object
			wstring sNamedItem = ACTIVS_HOST;
			T* pT = static_cast<T*>(this);
			m_mapNamedItems.insert(IUnknownPtrMap::value_type(sNamedItem,pT->GetUnknown()) );

			// Add Top-level Global Named Item
			if (SUCCEEDED(hr))
				hr = m_spAxsScript->AddNamedItem(ACTIVS_HOST, SCRIPTITEM_NAMEDITEM);

			if (SUCCEEDED(hr))
				return TRUE;

			m_mapNamedItems.erase( sNamedItem );
			ReleaseObjects();
		}
		return FALSE;
	}

	///////////////////////////////////////////////////////////////////////////
	// AddScriptItem
	BOOL AddScriptItem(LPCOLESTR pstrNamedItem, LPUNKNOWN lpUnknown, DWORD dwFlags = SCRIPTITEM_NAMEDITEM, BOOL bGlobalCollection = FALSE)
	{
		// Add Top-level Global Named Item
		if (m_spAxsScript != NULL && lpUnknown != NULL)
		{
			HRESULT hr;
			USES_CONVERSION;

			// Insert object into map
			wstring sNamedItem = pstrNamedItem;
			m_mapNamedItems.insert(IUnknownPtrMap::value_type(sNamedItem,lpUnknown) );
			if (bGlobalCollection)
			{
				dwFlags |= SCRIPTITEM_GLOBALMEMBERS;
			}
			hr = m_spAxsScript->AddNamedItem(pstrNamedItem, dwFlags);

			if (SUCCEEDED(hr))
				return TRUE;

			m_mapNamedItems.erase( sNamedItem );
		}
		return FALSE;
	}

	///////////////////////////////////////////////////////////////////////////
	// AddScriptCode
	BOOL AddScriptCode(LPCOLESTR pstrScriptCode)
	{
		if (m_spAxsScript != NULL && pstrScriptCode != NULL)
		{
			HRESULT hr;
			EXCEPINFO ei = { 0 };
			hr = m_spAxsParse->ParseScriptText(pstrScriptCode, 0, 0, 0, 0, 0, SCRIPTTEXT_ISPERSISTENT|SCRIPTTEXT_ISVISIBLE, 0, &ei);
			if (SUCCEEDED(hr))
				return TRUE;
		}
		return FALSE;
	}

	///////////////////////////////////////////////////////////////////////////
	// AddScriptlet
	BOOL AddScriptlet(LPCOLESTR pstrDefaultName, LPCOLESTR pstrCode, LPCOLESTR pstrItemName, LPCOLESTR pstrEventName)
	{
		if (m_spAxsParse != NULL)
		{
			HRESULT hr;
			EXCEPINFO ei = { 0 };
			BSTR bstrName = NULL;
			hr = m_spAxsParse->AddScriptlet(pstrDefaultName, 
				pstrCode,
				pstrItemName, // name object
				pstrItemName, // no subobject - same as object
				pstrEventName, // event name - attach to 'pstrItemName'
				L"", 
				0, 
				0,
				0, 
				&bstrName,
				&ei);
			SysFreeString(bstrName);
			if (SUCCEEDED(hr))
				return TRUE;
		}
		return FALSE;
	}

	// Helper functions
	///////////////////////////////////////////////////////////////////////////
	// CreateObjectHelper
	LPDISPATCH CreateObjectHelper(LPCOLESTR bstrProgID)
	{
		LPDISPATCH lpObject = NULL;
		CLSID clsidObject = CLSID_NULL;
		HRESULT hr = CLSIDFromProgID(bstrProgID, &clsidObject );
		if( SUCCEEDED( hr ) )
		{
			// Only Local-Server is safe (run as separated process)
			hr = CoCreateInstance(clsidObject, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void**)&lpObject );
			if (FAILED(hr))
			{
				ATLTRACE(L"(Script) Failed to create object Return = 0x%x\n", hr);
			}
		}
		else
		{
			ATLTRACE(L"(Script) Invalid ProgID\n");
		}

		return lpObject;
	}

	///////////////////////////////////////////////////////////////////////////
	// ReadTextFileHelper
	WCHAR*	ReadTextFileHelper(LPCOLESTR strFileName)
	{
		WCHAR *pwszResult = NULL;
		char szFileNameA[MAX_PATH];
		if (wcstombs(szFileNameA, strFileName, MAX_PATH) == -1)
			return pwszResult;

		HANDLE hfile = CreateFileA(szFileNameA, GENERIC_READ,
			FILE_SHARE_READ, 0, OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL, 0);
		if (hfile != INVALID_HANDLE_VALUE)
		{
			DWORD cch = GetFileSize(hfile, 0);
			char *psz = (char*)CoTaskMemAlloc(cch + 1);

			if (psz)
			{
				DWORD cb;
				ReadFile(hfile, psz, cch, &cb, 0);
				pwszResult = (WCHAR*)CoTaskMemAlloc((cch + 1)*sizeof(WCHAR));
				if (pwszResult)
					mbstowcs(pwszResult, psz, cch + 1);
				pwszResult[cch] = 0;
				CoTaskMemFree(psz);
			}
			CloseHandle(hfile);
		}
		return pwszResult;
	}

	///////////////////////////////////////////////////////////////////////////
	// DestroyDataHelper
	void DestroyDataHelper(LPVOID lpvData)
	{
		CoTaskMemFree(lpvData);
	}

	///////////////////////////////////////////////////////////////////////////
	// InvokeFuncHelper
	HRESULT InvokeFuncHelper(LPCOLESTR lpszName, VARIANT* pvarParams, int nParams, VARIANT* pvarRet = NULL)
	{
		HRESULT hr = E_FAIL;
		DISPID dispid;
		if (m_spAxsScript != NULL)
		{
			LPDISPATCH pDisp = NULL;
			DISPPARAMS dispparams = { pvarParams, NULL, nParams, 0};
			hr = m_spAxsScript->GetScriptDispatch(0, &pDisp);
			if (SUCCEEDED(hr))
				hr = pDisp->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpszName, 1, LOCALE_SCRIPT_DEFAULT, &dispid);
			if (SUCCEEDED(hr))
				hr = pDisp->Invoke(dispid, IID_NULL, LOCALE_SCRIPT_DEFAULT, DISPATCH_METHOD, &dispparams, pvarRet, NULL, NULL);
		}
		return hr;
	}

protected:
	CComPtr<IActiveScriptParse> m_spAxsParse;	// Active Script parse object
	CComPtr<IActiveScript>		m_spAxsScript;	// Active Script
	IUnknownPtrMap				m_mapNamedItems; // Named items map
};


#endif //_ATLACTIVESCRIPTSITE_H__